#include "collectionview.h"
#include <QPaintEvent>
#include <QPainter>
#include <QTimer>
#include <QAction>
#include <QGradient>
#include <QApplication>
#include <QUrl>
#include <QRgb>
#include "mediaitem.h"
#include "iconcache.h"
#include "library.h"
#include "mediaitemimpl.h"
#include "piocoreimpl.h"
#include <cmath>

CollectionView::CollectionView(QWidget * parent)
: QWidget(parent),
  m_core(NULL),
  m_cache(NULL),
  //m_selectedItem(0),
  //m_selectionStartItem(0),
  m_item_width(256),
  m_item_height(256*3/4),
  m_margin(10),
  m_title_height(32),
  m_update_scheduled(false),
  m_restore_vertical_position(false),
  m_grouping(GroupDay),
  m_crop(true),
  m_cacheQueueStatus(false)
{
    m_title_font.setBold(true);
    m_tags_font.setPixelSize(10);
    m_restore_vertical_position = false;
    m_color1 = palette().color(QPalette::Highlight);// QColor(180, 200, 255);
    m_color2 = QColor(30, 30, 30);
    m_check = QPixmap::fromImage(QImage(":/images/check.png").scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
    QTimer::singleShot(5000, this, SLOT(checkCacheStatus()));
}

void CollectionView::setCore(PioCore * core)
{
    m_core = core;
    connect(m_core, SIGNAL(selectionChanged(QList<MediaItemPtr>)), this, SLOT(onSelectionChanged(QList<MediaItemPtr>)));
}

void CollectionView::setIconCache(IconCache * cache)
{
    m_cache = cache;
    connect(cache, SIGNAL(loaded(MediaItemPtr,QSize,QSharedPointer<QPixmap>)),
            this, SLOT(onLoaded(MediaItemPtr,QSize,QSharedPointer<QPixmap>)));
}

/*QSet<MediaItemPtr> CollectionView::selection()
{
    QSet<MediaItemPtr> items;
    QSetIterator<Item*> it(m_selection);
    while(it.hasNext()) {
        items.insert(it.next()->mi);
    }
    return items;
}*/

/*void CollectionView::select(QSet<MediaItemPtr> selection)
{
    QSet<Item*> tmp;
    foreach(MediaItemPtr item, selection) {
        if(m_items.contains(item))
            tmp.insert(m_items[item]);
    }
    m_selection = tmp;
    emit selectionChanged(selection);
}*/

void CollectionView::onAdd(MediaItemPtr item)
{
    if(m_items.contains(item))
        return;
    m_items.append(item);
    schedule_update();
}

void CollectionView::onRemove(MediaItemPtr item)
{
    if(!m_items.contains(item))
        return;

    if(m_selection.size() == 1 && m_selection.contains(item))
        right();

    m_items.removeAll(item);
    schedule_update();
}

void CollectionView::onUpdate(MediaItemPtr item)
{
    if(!m_items.contains(item))
        return;
    schedule_update();
}

void CollectionView::large_thumbnails()
{
    m_item_width = 256;
    m_item_height = 256 * 3 / 4;
    m_crop = false;
    m_restore_vertical_position = true;
    schedule_update();
}

void CollectionView::medium_thumbnails()
{
    m_item_width = 128;
    m_item_height = 128;
    m_crop = true;
    m_restore_vertical_position = true;
    schedule_update();
}

void CollectionView::small_thumbnails()
{
    m_item_width = 64;
    m_item_height = 64;
    m_crop = true;
    m_restore_vertical_position = true;
    schedule_update();
}

void CollectionView::onSetVerticalPosition(int y)
{
    m_verticalPosition = y;
}

void CollectionView::paintEvent(QPaintEvent * e)
{    
    QRect repaint_rect = e->rect();

    QPainter p(this);
    p.fillRect(repaint_rect, m_color2);

    foreach(Header header, m_headers) {
        if(!header.rect.intersects(repaint_rect))
            continue;
        p.fillRect(header.rect.adjusted(-1,1,1,-1), m_gradient);
        QRect tr = header.rect.adjusted(m_margin, m_margin, -m_margin, -m_margin);
        p.setPen(m_color1);
        p.setFont(m_title_font);
        p.drawText(tr, Qt::AlignVCenter, header.title);
        p.setFont(m_tags_font);
        QStringList tags = header.tags.toList();
        p.drawText(tr, Qt::AlignVCenter | Qt::AlignRight, tags.join(", "));
        p.setPen(Qt::black);
    }

    foreach(MediaItemPtr item, m_items) {
        QRect rect = item->impl()->m_rect;
        if(rect.isNull() || !rect.adjusted(-4, -4, 4, 4).intersects(repaint_rect))
            continue;

        bool selected = item->isSelected();
        //int x = item->rect.center().x();
        //int y = item->rect.center().y();
        //int w = m_item_width;
        //int h = m_item_height;
        QPixmap pm = m_core->icon(item, QSize(m_item_width, m_item_height), false);
        if(pm.isNull())
            rect = rect.adjusted(4, 4, -4, -4);
        else
            rect = QRect(rect.center().rx() - pm.width() / 2, rect.center().ry() - pm.height() / 2, pm.width(), pm.height());
        /*if(m_crop) {
            img = img.scaled(w, h, Qt::KeepAspectRatioByExpanding);
            img = img.copy((img.width()-w) / 2, (img.height() - h)/2, w, h);
            addRoundCorners(img, 10);
        }
        else
            img = img.scaled(w, h, Qt::KeepAspectRatio);
        item->thumbnail = QPixmap::fromImage(img);*/

        if(selected)
            p.fillRect(rect.adjusted(-2, -2, 2, 2), m_color1);

        if(pm.isNull()) {
            p.setPen(m_color1);
            p.setFont(m_tags_font);
            p.drawText(rect, Qt::AlignCenter | Qt::AlignHCenter, tr("loading..."));
        }
        else {
            p.drawPixmap(rect.topLeft(), pm);
            if(item->metadata().isRaw) {
                p.setFont(m_tags_font);
                p.setPen(m_color2);
                p.drawText(rect.adjusted(1, 1, 0, 0), Qt::AlignLeft | Qt::AlignBottom, "RAW");
                p.setPen(m_color1);
                p.drawText(rect.adjusted(2, 2, 0, 0), Qt::AlignLeft | Qt::AlignBottom, "RAW");
            }
        }
        if(item->isChecked()) {
            QPoint x = rect.bottomRight() - QPoint(m_check.width(), m_check.height());
            p.drawPixmap(x, m_check);
        }
/*        if(item == m_cursor) {
            p.setBrush(Qt::NoBrush);
            p.setPen(QPen(m_color1, 1, Qt::DashLine));
            p.drawRect(rect.adjusted(0, 0, -1, -1));
        }*/
    }

    if(m_mouseAction == SelectItems) {
        QColor tmp = m_color1;
        tmp.setAlpha(64);
        //p.fillRect(rect, tmp);
        p.setBrush(tmp);
        QPen pen(Qt::DashLine);
        pen.setColor(m_color1);
        p.setPen(pen);
        p.drawRect(m_selection_rect);
    }
}

void CollectionView::resizeEvent(QResizeEvent * e)
{
    if(e->oldSize().width() != e->size().width())
        schedule_update();
}

void CollectionView::mouseDoubleClickEvent(QMouseEvent * e)
{
    m_mouseAction = DoubleClick;
    foreach(Header header, m_headers) {
        if(header.rect.contains(e->pos())) {
            m_core->select(QSet<MediaItemPtr>::fromList(header.items));
            return;
        }
    }

    MediaItemPtr item = itemAt(e->pos());
    if(item.isNull()) {
        m_core->select(QSet<MediaItemPtr>());
    }
    else {
        m_core->select(QSet<MediaItemPtr>() << item);
        emit view(item);
    }
}

void CollectionView::mousePressEvent(QMouseEvent * e)
{
    m_start_pos = e->pos();
    m_start_buttons = e->buttons();
    m_mouseAction = Click;
}

void CollectionView::mouseMoveEvent(QMouseEvent * e)
{
    if(m_mouseAction == Click && (e->buttons() & Qt::LeftButton) && (m_start_pos - e->pos()).manhattanLength() >= QApplication::startDragDistance()) {
        MediaItemPtr item = itemAt(m_start_pos);
        if(item.isNull()) {
            m_mouseAction = SelectItems;
            m_selection_rect = QRect(m_start_pos, e->pos());
            update();
        }
        else {
            m_mouseAction = DragItems;
            if(!item->isSelected()) {
                m_core->clearSelection();
                m_core->select(item, true);
            }
            update();

            // get selection and their different versions (sets)
            QList<MediaItemPtr> sel = m_core->selection();
            QSet<MediaItemPtr> set;
            foreach(MediaItemPtr i, sel) {
                set += i;
                //set += m_core->set(i);
            }

            // get urls for these items
            QList<QUrl> urls;
            foreach(MediaItemPtr i, set) {
                urls.append(QUrl::fromLocalFile(i->metadata().filename));
            }

            // initiate drag and drop
            QDrag * drag = new QDrag(this);
            QMimeData * mimeData = new QMimeData;

            mimeData->setUrls(urls);
            drag->setMimeData(mimeData);
            drag->exec(Qt::CopyAction);
        }
    }
    else if(m_mouseAction == SelectItems) {
        m_selection_rect.setBottomRight(e->pos());
        update();
    }
}

void CollectionView::mouseReleaseEvent(QMouseEvent * e)
{
    if(m_mouseAction == Click) {
        MediaItemPtr item = itemAt(m_start_pos);
        if(m_start_buttons == Qt::RightButton) {
            if(!item.isNull() && !item->isSelected())
                m_core->select(QSet<MediaItemPtr>() << item);
        }
        else
            select(item, getSelectionMode(e->modifiers()));
    }
    else if(m_mouseAction == SelectItems) {
        QList<MediaItemPtr> items = itemsAt(m_selection_rect);
        QSet<MediaItemPtr> sel;
        if(e->modifiers() & Qt::ShiftModifier) {
            sel = m_core->selection().toSet().unite(items.toSet());
        }
        else if(e->modifiers() & Qt::ControlModifier) {
            sel = m_core->selection().toSet().subtract(items.toSet());
        }
        else {
            sel = items.toSet();
        }
        m_core->select(sel);
    }
    m_mouseAction = Click;
    update();
}

void CollectionView::contextMenuEvent(QContextMenuEvent *)
{
    emit contextMenuRequested();
}

void CollectionView::keyPressEvent(QKeyEvent * e)
{
    if(e->key() == Qt::Key_Left) {
        left();
        e->accept();
    }
    else if(e->key() == Qt::Key_Right) {
        right();
        e->accept();
    }
    else if(e->key() == Qt::Key_Up) {
        up();
        e->accept();
    }
    else if(e->key() == Qt::Key_Down) {
        down();
        e->accept();
    }
    else if(e->key() == Qt::Key_Home) {
        begin();
        e->accept();
    }
    else if(e->key()== Qt::Key_End) {
        end();
        e->accept();
    }
    else if(e->key() == Qt::Key_Escape) {
        emit fullscreen(false);
        e->accept();
    }
    else if(e->key() == Qt::Key_Return) {
        if(m_selection.size() == 1)
            emit view(*m_selection.begin());
    }
/*    else if(e->key() == Qt::Key_Space) {
        if(!m_cursor.isNull())
            m_core->select(m_cursor, !m_cursor->isSelected());
    }*/
    else
        QWidget::keyPressEvent(e);
}

void CollectionView::checkCacheStatus()
{
    int n = m_cache->queueSize();
    if(n > 100) {
        emit status(QString("Creating %1 previews...").arg(n));
        m_cacheQueueStatus = true;
    }
    else if(m_cacheQueueStatus) {
        emit status("Ok.");
        m_cacheQueueStatus = false;
    }
    QTimer::singleShot(5000, this, SLOT(checkCacheStatus()));
}

void CollectionView::makeVisible(MediaItemPtr item)
{
    QMap<MediaItemPtr, int>::iterator it = m_headermap.find(item);
    if(it != m_headermap.end())
        emit makeVisible(m_headers[it.value()].rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
    emit makeVisible(item->impl()->m_rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
}

void CollectionView::updateItem(MediaItemPtr item)
{
    update(item->impl()->m_rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
}

void CollectionView::updateGeom()
{
    m_update_scheduled = false;

/*    Item * firstVisibleItem = 0;
    int firstVisibleItemOffset = 0;
    if(m_restore_vertical_position) {
        for(int i=0; i<m_sortedItems.size(); i++) {
            if(m_sortedItems[i]->rect.top() >= m_verticalPosition) {
                firstVisibleItem = m_sortedItems[i];
                firstVisibleItemOffset = firstVisibleItem->rect.top() - m_verticalPosition;
                break;
            }
        }
        m_restore_vertical_position = false;
    }*/

    int group_width = width() - 2 * m_margin;
    if(group_width < m_item_width)
        group_width = m_item_width;
    int items_per_row = (group_width + m_margin) / (m_item_width + m_margin);
    int row_spacing = (!m_crop || items_per_row < 2) ? m_margin : (group_width - items_per_row * m_item_width) / (items_per_row - 1);

    QLinearGradient gradient(0, 0, width(), 0);
    gradient.setColorAt(0, color(0.5));
    gradient.setColorAt(1, m_color2);
    m_gradient = QBrush(gradient);

    int y = row_spacing;
    int col = 0;

    m_headers.clear();
    m_headermap.clear();

    qSort(m_items.begin(), m_items.end(), PioCoreImpl::newer);
    MediaItemPtr prev;
    bool firstrow = false;
    foreach(MediaItemPtr item, m_items) {
        bool is_group = prev.isNull() || split(*prev, *item);
        if(is_group) {
            if(col) {
                y += m_item_height + row_spacing;
                col = 0;
            }
            Header header;
            header.title = item->metadata().created.date().toString();
            if(header.title.isEmpty())
                header.title = tr("Without date");
            header.rect = QRect(0, y, width(), m_title_height);
            y += m_title_height + row_spacing;

            m_headers.push_back(header);
            firstrow = true;
        }
        Header & header = m_headers.last();
        header.items.append(item);
        if(firstrow)
            m_headermap[item] = m_headers.size() - 1;
        if(item->metadata().collection.isEmpty())
            header.tags.insert(tr("my collection"));
        else
            header.tags.insert(item->metadata().collection);

        int x = m_margin + ((items_per_row <= 1 || col==0) ? 0 : ((group_width - m_item_width) * col / (items_per_row - 1)));

        item->impl()->m_rect = QRect(x, y, m_item_width, m_item_height);
        col++;
        while(col>=items_per_row) {
            col -= items_per_row;
            y += m_item_height + row_spacing;
            firstrow = false;
        }
        prev = item;
    }

    if(col) {
        y += m_item_height + row_spacing;
        col = 0;
    }

    if(height()!=y)
        setMinimumHeight(y);

    //if(firstVisibleItem)
    //    emit setVerticalPosition(firstVisibleItem->rect.top() - firstVisibleItemOffset);

    update();
}

void CollectionView::onSelectionChanged(QList<MediaItemPtr> selection)
{
    QSet<MediaItemPtr> prev = m_selection;
    m_selection = selection.toSet();
    if(m_selection.size() == 1)
        makeVisible(*m_selection.begin());
    update();
/*    foreach(MediaItemPtr item, prev)
        if(!m_selection.contains(item))
            updateItem(item);
    foreach(MediaItemPtr item, m_selection)
        if(!prev.contains(item))
            updateItem(item);*/
}

/*void CollectionView::onCursorChanged(MediaItemPtr cursor)
{
    MediaItemPtr oldcursor = m_cursor;
    m_cursor = cursor;
    if(!m_cursor.isNull()) {
        QMap<MediaItemPtr, int>::iterator it = m_headermap.find(cursor);
        if(it != m_headermap.end())
            emit makeVisible(m_headers[it.value()].rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
        emit makeVisible(m_cursor->impl()->m_rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
        update(m_cursor->impl()->m_rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
    }
    if(!oldcursor.isNull())
        update(oldcursor->impl()->m_rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
}*/

void CollectionView::onLoaded(MediaItemPtr item, QSize , QSharedPointer<QPixmap> icon)
{
    QMutexLocker l(&m_mutex);
    item->impl()->m_icon = icon;
    update(item->impl()->m_rect.adjusted(-m_margin, -m_margin, m_margin, m_margin));
}

void CollectionView::left()
{
    if(m_items.isEmpty())
        return;

    MediaItemPtr c = cursor();
    if(c.isNull())
        select(m_items.first());
    else {
        MediaItemPtr prev;
        foreach(MediaItemPtr item, m_items) {
            if(item == c && !prev.isNull()) {
                select(prev);
                break;
            }
            prev = item;
        }
    }
}

void CollectionView::right()
{
    if(m_items.isEmpty())
        return;

    MediaItemPtr c = cursor();
    if(c.isNull()) {
        select(m_items.first());
    }
    else {
        MediaItemPtr prev;
        foreach(MediaItemPtr item, m_items) {
            if(prev == c) {
                select(item);
                break;
            }
            prev = item;
        }
    }
}

void CollectionView::up()
{
    if(m_items.isEmpty())
        return;

    MediaItemPtr c = cursor();
    if(c.isNull()) {
        select(m_items.first());
    }
    else {
        MediaItemPtr n;
        QRect & rc = c->impl()->m_rect;
        foreach(MediaItemPtr item, m_items) {
            QRect & ri = item->impl()->m_rect;
            if(ri.bottom() < rc.top() && ri.left() < rc.right())
                n = item;
        }
        if(!n.isNull())
            select(n);
    }
}

void CollectionView::down()
{
    if(m_items.isEmpty())
        return;

    MediaItemPtr c = cursor();
    if(c.isNull()) {
        select(m_items.first());
    }
    else {
        MediaItemPtr n;
        QRect & rc = c->impl()->m_rect;
        foreach(MediaItemPtr item, m_items) {
            QRect & ri = item->impl()->m_rect;
            if(ri.top() > rc.bottom() && ri.right() > rc.left()) {
                n = item;
                break;
            }
        }
        if(!n.isNull())
            select(n);
    }
}

void CollectionView::begin()
{
    if(!m_items.isEmpty())
        select(m_items.first());
}

void CollectionView::end()
{
    if(!m_items.isEmpty())
        select(m_items.last());
}

void CollectionView::schedule_update()
{
    if(m_update_scheduled)
        return;
    m_update_scheduled = true;
    QTimer::singleShot(500, this, SLOT(updateGeom()));
}

bool CollectionView::split(const MediaItem & a, const MediaItem & b)
{
    switch(m_grouping) {
        case GroupEvent:
            return std::abs((double)(a.metadata().created.daysTo(b.metadata().created))) > 1;
        default:
            return a.metadata().created.date() != b.metadata().created.date();
    }
}

QColor CollectionView::color(double val)
{
    return QColor(m_color1.red() * (1-val) + m_color2.red() * val,
                  m_color1.green() * (1-val) + m_color2.green() * val,
                  m_color1.blue() * (1-val) + m_color2.blue() * val);
}

MediaItemPtr CollectionView::cursor()
{
    if(m_selection.empty())
        return MediaItemPtr();
    MediaItemPtr r = *m_selection.begin();
    foreach(MediaItemPtr item, m_selection)
        if(PioCoreImpl::newer(item, r))
            r = item;
    return r;
}

MediaItemPtr CollectionView::itemAt(const QPoint & pos)
{
    foreach(MediaItemPtr item, m_items) {
        if(item->impl()->m_rect.contains(pos))
            return item;
    }
    return MediaItemPtr();
}

QList<MediaItemPtr> CollectionView::itemsAt(const QRect & rect)
{
    QList<MediaItemPtr> items;
    foreach(MediaItemPtr item, m_items) {
        if(item->impl()->m_rect.intersects(rect))
            items.append(item);
    }
    return items;
}

void CollectionView::select(MediaItemPtr & item, SelectionMode mode)
{
    if(mode == SetSelection) {
        if(item.isNull())
            m_core->clearSelection();
        else
            m_core->select(QSet<MediaItemPtr>() << item);
    }
    else if(mode == AddSelection && !item.isNull()) {
        m_core->select(item, true);
    }
    else if(mode == ToggleSelection && !item.isNull()) {
        m_core->select(item, !item->isSelected());
    }
    update();
}

CollectionView::SelectionMode CollectionView::getSelectionMode(Qt::KeyboardModifiers m)
{
    if(m & Qt::ControlModifier)
        return ToggleSelection;
    else if(m & Qt::ShiftModifier)
        return AddSelection;
    else
        return SetSelection;
}
